<?php

/*
 * Copyright 2008 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#namespace tehframework\annotations\parsers;

#use tehframework\testing\phpunit\TestCase;

require_once 'test/testHelper.php';

class ArrayParserImplTest extends TestCase
{
	/**
	 * @var ArrayParserImpl
	 */
	protected $parser;
	
	/**
	 * @var ConstantValueParser
	 */
	protected $constantParser;
	
	/**
	 * @var AnnotationParser
	 */
	protected $annotationParser;
	
	protected function setUp()
	{
		$this->constantParser   = $this->mock('ConstantValueParser');
		$this->annotationParser = $this->mock('AnnotationParser');
		$this->parser           = new ArrayParserImpl($this->constantParser);
	}
	
	function testParseArrayValueThrowsExceptionIfConstantValueParserWasNotSpecified()
	{
		$this->setExpectedException('ParserException');
		
		$parser = new ArrayParserImpl();
		$parser->parseArray('{one: 1}');
	}
	
	function testParseArrayParsesWithoutKeyRestrictionsByDefault()
	{
		$string = '{_: 1, -10: 2, $key: "value"}';
		$array  = array('_' => 1, -10 => 2, '$key' => 'value');
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)->parseConstantValue('1')->returns(1)
			->one($this->constantParser)->parseConstantValue('2')->returns(2)
			->one($this->constantParser)
				->parseConstantValue('"value"')->returns('value');
		
		$this->assertSame($array, $this->parser->parseArray($string));
	}
	
	function testParseArrayThrowsExceptionIfKeyDoesNotConformToGivenRestrictions()
	{
		$this->setExpectedException('ParserException');
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)->parseConstantValue('1')->returns(1);
		
		$this->parser
			->withKeyRestrictions(ArrayParserImpl::DEFAULT_KEY_RESTRICTIONS)
			->parseArray('{_: 1, -10: 2, $key: "value"}');
	}
	
	function testParseArrayAllowsMixedArraysByDefualt()
	{
		$string = '{k1: 1, 2, k2: "value"}';
		$array  = array('k1' => 1, 2, 'k2' => 'value');
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)->parseConstantValue('1')->returns(1)
			->one($this->constantParser)->parseConstantValue('2')->returns(2)
			->one($this->constantParser)
				->parseConstantValue('"value"')->returns('value');
		
		$this->assertSame($array, $this->parser->parseArray($string));
	}
	
	function testParseArrayThrowsExceptionIfArrayIsMixedButNotAllowed()
	{
		$this->setExpectedException('ParserException');
		
		$string = '{k1: 1, 2, k2: "value"}';
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)->parseConstantValue('1')->returns(1);
		
		$this->parser
			->disallowMixedArrays()
			->parseArray($string);
	}
	
	function testParseArrayThrowsExceptionIfArrayIsMixedButNotAllowed2()
	{
		$this->setExpectedException('ParserException');
		
		$string = '[k1: 1, 2, k2: "value"]';
		
		$this->expect
			->never($this->annotationParser)
			->never($this->constantParser);
		
		$this->parser
			->disallowMixedArrays()
			->parseArray($string);
	}
	
	function testParseArrayAllowsMixedArraysOnLevel1Only()
	{
		$string = '[k1: 1, 2, {level: 2}, ["level 2", {level: 3}]]';
		$array  = array(
			'k1' => 1, 2, array('level' => 2), array('level 2', array('level' => 3)));
		
		$this->expect
			->never($this->annotationParser)
			->exactly(2)->of($this->constantParser)
				->parseConstantValue('2')->returns(2)
			->one($this->constantParser)
				->parseConstantValue('"level 2"')->returns('level 2')
			->one($this->constantParser)->parseConstantValue('1')->returns(1)
			->one($this->constantParser)->parseConstantValue('3')->returns(3);
		
		$result = $this->parser
			->allowMixedArraysAtFirstLevel()->parseArray($string);
		
		$this->assertSame($array, $result);
	}
	
	function testParseArrayAllowsMixedArraysOnLevel1Only2()
	{
		$this->setExpectedException('ParserException');
		
		$string = '[k1: 1, 2, {level: 2}, [invalid: "level 2", {level: 3}]]';
		
		$this->expect
			->never($this->annotationParser)
			->exactly(2)->of($this->constantParser)
				->parseConstantValue('2')->returns(2)
			->one($this->constantParser)->parseConstantValue('1')->returns(1);
		
		$this->parser
			->allowMixedArraysAtFirstLevel()
			->parseArray($string);
	}
	
	function testParseArrayDisallowsItemSkippingByDefualt()
	{
		$this->setExpectedException('ParserException');
		
		$string = '{k1: 1, , k2: "value"}';
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)->parseConstantValue('1')->returns(1);
		
		$this->parser->parseArray($string);
	}
	
	function testParseArraySkipsItemsIfAllowed()
	{
		$string = '{k1: 1, , , , , k2: "value"}';
		$array  = array('k1' => 1, null, null, null, null, 'k2' => 'value');
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)->parseConstantValue('1')->returns(1)
			->one($this->constantParser)
				->parseConstantValue('"value"')->returns('value');
		
		$this->assertSame($array,
			$this->parser->allowItemSkipping()->parseArray($string));
	}
	
	function testParseArrayThrowsExceptionIfKeyIsMalformed()
	{
		$this->setExpectedException('ParserException');
		
		$string = '{: "empty key specified"}';
		
		$this->expect
			->never($this->annotationParser)
			->never($this->constantParser);
		
		$this->parser->parseArray($string);
	}
	
	function testParseArrayThrowsExceptionIfKeyIsMalformed2()
	{
		$this->setExpectedException('ParserException');
		
		$string = '{double:colon: "two keys specified"}';
		
		$this->expect
			->never($this->annotationParser)
			->never($this->constantParser);
		
		$this->parser->parseArray($string);
	}
	
	function testParseArrayThrowsExceptionIfKeyIsMalformed3()
	{
		$this->setExpectedException('ParserException');
		
		$string = '{@Fake: "annotation as key"}';
		
		$this->expect
			->never($this->annotationParser)
			->never($this->constantParser);
		
		$this->parser->parseArray($string);
	}
	
	function testParseArrayIgnoresComments()
	{
		$string = '{/* at the beginning */one: 1, 2 /* ' .
			'{key: "va\"lue", 1, @Fake<MoreFake>(), ' .
			'foo=PDO.PARAM_BOOL, [{}, {}]} *//* test: 2 */, ' .
			'three: 3/* and at the end */}';
		$array  = array('one' => 1, 2, 'three' => 3);
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)->parseConstantValue('1')->returns(1)
			->one($this->constantParser)->parseConstantValue('2')->returns(2)
			->one($this->constantParser)->parseConstantValue('3')->returns(3);
		
		$this->assertSame($array, $this->parser->parseArray($string));
	}
	
	function testParseArrayParsesStringsInItemsValues()
	{
		$string = '["simple", complex: "{}/* comment */[].:=\\"", ' .
			'escaped: \'""\\\'\']';
		$array  = array(
			'simple', 'complex' => '{}/* comment */[].:="', 'escaped' => '""\'');
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)
				->parseConstantValue('"simple"')->returns('simple')
			->one($this->constantParser)
				->parseConstantValue('"{}/* comment */[].:=\""')
				->returns('{}/* comment */[].:="')
			->one($this->constantParser)
				->parseConstantValue('"\"\"\\\'"')->returns('""\'');
		
		$this->assertSame($array, $this->parser->parseArray($string));
	}
	
	function testParseArrayThrowsExceptionIfEscapeCharIsWronglyUsed()
	{
		$this->setExpectedException('ParserException');
		
		$string = '[\: 1]';
		
		$this->expect
			->never($this->annotationParser)
			->never($this->constantParser);
		
		$this->parser->parseArray($string);
	}
	
	function testParseArrayParsesEmptyArrays()
	{
		$strings = array(
			'[]',
			"   \n\n\t[] \n",
			"[\t\t\n\n  ]", '{}',
			"   \n\n\t{} \n", "{\t\t\n\n  } ");
		
		$this->expect
			->never($this->annotationParser)
			->never($this->constantParser);
		
		foreach ($strings as $string)
		{
			$this->assertSame(array(), $this->parser->parseArray($string));
		}
	}
	
	function testParseArrayDoesNotEscapeNonSpecialCharacters()
	{
		$string = '{pattern="/entity/(?P<id>\d+)", method="POST"}';
		$array  = array('pattern' => '/entity/(?P<id>\d+)', 'method' => 'POST');
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)
				->parseConstantValue('"/entity/(?P<id>\\d+)"')
				->returns($array['pattern'])
			->one($this->constantParser)
				->parseConstantValue('"POST"')
				->returns($array['method']);
		
		$this->assertSame($array, $this->parser->parseArray($string));
	}
	
	function testParseArrayThrowsExceptionIfValueDoesNotBeginWithCorrectBracket()
	{
		$this->setExpectedException('ParserException');
		
		$string = '';
		
		$this->expect
			->never($this->annotationParser)
			->never($this->constantParser);
		
		$this->parser->parseArray($string);
	}
	
	function testParseArrayThrowsExceptionIfValueDoesNotBeginWithCorrectBracket2()
	{
		$this->setExpectedException('ParserException');
		
		$string = 'Not a bracket[1,2,3]';
		
		$this->expect
			->never($this->annotationParser)
			->never($this->constantParser);
		
		$this->parser->parseArray($string);
	}
	
	function testParseArrayThrowsExceptionIfValueDoesNotEndWithBracket()
	{
		$this->setExpectedException('ParserException');
		
		$string = '[1, 2, 3]Not a bracket';
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)->parseConstantValue('1')->returns(1)
			->one($this->constantParser)->parseConstantValue('2')->returns(2)
			->one($this->constantParser)->parseConstantValue('3')->returns(3);
		
		$this->parser->parseArray($string);
	}
	
	function testParseArrayThrowsExceptionOnCurlyBracketMismatch()
	{
		$this->setExpectedException('ParserException');
		
		$string = '[1, 2, {{3}]]';
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)->parseConstantValue('1')->returns(1)
			->one($this->constantParser)->parseConstantValue('2')->returns(2)
			->one($this->constantParser)->parseConstantValue('3')->returns(3);
		
		$this->parser->parseArray($string);
	}
	
	function testParseArrayThrowsExceptionOnSquareBracketMismatch()
	{
		$this->setExpectedException('ParserException');
		
		$string = '[1, 2, [[3]}]';
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)->parseConstantValue('1')->returns(1)
			->one($this->constantParser)->parseConstantValue('2')->returns(2)
			->one($this->constantParser)->parseConstantValue('3')->returns(3);
		
		$this->parser->parseArray($string);
	}
	
	function testParseArrayThrowsExceptionIfFinalArrayNestingLevelIsNot0()
	{
		$this->setExpectedException('ParserException');
		
		$string = '[1, 2, {{3}}';
		
		$this->expect
			->never($this->annotationParser)
			->one($this->constantParser)->parseConstantValue('1')->returns(1)
			->one($this->constantParser)->parseConstantValue('2')->returns(2)
			->one($this->constantParser)->parseConstantValue('3')->returns(3);
		
		$this->parser->parseArray($string);
	}
	
	function testParseArraySkipsAnnotationsInValuesByDefault()
	{
		$string = '[@Fake, @Fake<MoreFake>, @Fake[fakeParam], ' .
			'@Fake<MoreFake>[fakeParam], @Fake(), @Fake(@MoreFake(1, 2, 3)), @Fake]';
		$array  = array(null, null, null, null, null, null, null);
		
		$this->expect
			->never($this->annotationParser)
			->never($this->constantParser);
		
		$this->assertSame($array, $this->parser->parseArray($string));
	}
	
	function testParseArrayParsesAnnotationsInValuesIfAllowed()
	{
		$string = '[@Fake, @Fake<MoreFake>, @Fake[fakeParam], ' .
			'@Fake<MoreFake>[fakeParam]]';
		$array  = array(true, true, true, true);
		
		$this->expect
			->never($this->constantParser)
			->exactly(4)->of($this->annotationParser)
				->parseAnnotation(Yay::pattern('#^@#'))->returns(true);
		
		$parser = new ArrayParserImpl(
			$this->constantParser, $this->annotationParser);
		
		$this->assertSame($array, $parser->parseArray($string));
	}
	
	function testParseArrayCorrectlyParsesNestedArrays()
	{
		$string = '[1, [1, [1, [1, [1, {six: 1, seven: [1, 8: [], 1]}, 1]]]], ' .
			'eleven: 1, twelve: {[web: 2.0]}]';
		$array  = array(
			1,
			array(1, array(1, array(1, array(1, array(
				'six' => 1, 'seven' => array(1, 8 => array(), 1)), 1)))),
			'eleven' => 1,
			'twelve' => array(array('web' => 2.0))
		);
		
		$this->expect
			->never($this->annotationParser)
			->exactly(10)->of($this->constantParser)
				->parseConstantValue('1')->returns(1)
			->one($this->constantParser)->parseConstantValue('2.0')->returns(2.0);
		
		$this->assertSame($array, $this->parser->parseArray($string));
	}
}